<?php
/**
 * Copyright (c) 2006-2008, Julien PORTALIER
 * http://featherphp.googlecode.com/
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 */

/**
 * Handles a collection.
 */
class ArrayCollection extends ArrayObject
{
	function __construct($data, $split=',;')
	{
		if (!is_array($data)) {
			$data = preg_split('/['.preg_quote($split).']/', $data, -1, PREG_SPLIT_NO_EMPTY);
		}
		foreach($data as $k => $v)
		{
			$v = trim($v);
			
			if (empty($v)) {
				unset($data[$k]);
			}
			else {
				$data[$k] = $v;
			}
		}
		$data = array_unique($data);
		parent::__construct($data);
	}
	
	function __toString()
	{
		ob_start();
		foreach($this as $value) {
			echo "$value, ";
		}
		return ob_get_clean();
	}
	
	function implode($sep='')
	{
		$data = array();
		foreach($this as $value) {
			$data[] = $value;
		}
		return implode($sep, $data);
	}
}

/**
 * Database ORM: handles collections, like tags.
 */
abstract class ActiveRecord_Collection extends ActiveRecord_Base
{
	protected $_coll_field;
	protected $_referer;
#	protected $_referer_field;
	
	protected $_process = DBO::PROCESS_LIST;
	protected $_extend  = false;
	
	function __construct()
	{
		$this->_name = get_class($this);
		
		# defines defaults
		if (empty($this->_coll_field)) {
			$this->_coll_field = Inflector::underscore($this->_name);
		}
		if ($this->_default_fields == '*') {
			$this->_default_fields = "DISTINCT {$this->_name}.{$this->_coll_field}";
		}
		if (empty($this->_order)) {
			$this->_order = "{$this->_name}.{$this->_coll_field} ASC";
		}
		
		# constructs record
		parent::__construct();
		
		# more defaults
		if (empty($this->_referer))
		{
			#$this->_referer = array_shift(array_keys($this->_associations));
			$this->_referer = array_shift(array_keys($this->_belongs_to));
		}
	}
	
	/**
	 * Returns the collection of elements associated to a referer.
	 * 
	 * @return ArrayCollection
	 * @param $referer_id Integer
	 */
	function get_collection($referer_id)
	{
		$options = array('conditions' => array(
			$this->_associations[$this->_referer]['foreign_key'] => $referer_id)
		);
		$collection = $this->find(':all', &$options);
		return empty($collection) ? '' : new ArrayCollection(&$collection);
	}
	
	/**
	 * Searches the elements that match a keyword.
	 * 
	 * @return Array
	 * @param $q String
	 * @param $limit Integer[optional]
	 */
	function & search($q, $limit=null)
	{
		$options = array(
			'fields'     => "DISTINCT {$this->_coll_field}",
			'conditions' => array($this->_coll_field => "LIKE $q%")
		);
		if ($limit) {
			$options['limit'] = $limit;
		}
		return $this->find(':all', &$options);
	}
	
	/**
	 * Returns a tagcloud ready associative array of elements.
	 * 
	 * @return Array
	 * @param $limit_or_options Mixed[optional]
	 */
	function & get_cloud($limit_or_options=null)
	{
		if (is_array($limit_or_options)) {
			$options =& $limit_or_options;
		}
		else
		{
			$options = empty($limit_or_options) ?
				array() :
				array('limit' => $limit_or_options);
		}
		
		# prepare query
		$options['fields'] = array(
			"{$this->_name}.{$this->_coll_field}",
			'-! COUNT(*) AS '.$this->_db->field('coll_counter')
#			'-! COUNT('.$this->_db->field("{$this->_referer}.{$this->_associations[$this->_referer]['foreign_key']}").') AS '.$this->db->field('coll_counter')
		);
		$options['group']   = "{$this->_name}.{$this->_coll_field}";
		$options['process'] = DBO::PROCESS_ASSOC_LIST;
		$options['order']   = 'coll_counter DESC';
		
		# query
		$cloud = $this->find_all(&$options);
		
		# sort tags by name
		ksort($cloud, SORT_LOCALE_STRING);
		return $cloud;
	}
	
	/**
	 * Returns a tagcloud ready associative array of elements from a given referer.
	 * 
	 * @return Array
	 * @param $collection Mixed
	 * @param $limit_or_options Mixed[optional]
	 */
	function get_cloud_related($collection, $limit_or_options=null)
	{
		$collection = array_collection($collection);
		
		if (is_array($limit_or_options)) {
			$options =& $limit_or_options;
		}
		else
		{
			$options = empty($limit_or_options) ?
				array() :
				array('limit' => $limit_or_options);
		}
		
		$options['conditions'] = array(
			"{$this->_name}.{$this->_associations[$this->_referer]['foreign_key']}" =>
				"-! IN ( ".$this->__format_sql($collection)." )",
			"{$this->_name}.{$this->_coll_field}" => array('NOT' => &$collection)
		);
		
		return $this->get_cloud(&$options);
	}
	
	function & get_find_options($collection, array $options=array())
	{
		$collection = array_collection($collection);
		if (count($collection) == 1)
		{
			# single tag
			$options['conditions']["{$this->_name}.{$this->_coll_field}"] = $collection[0];
			$options['include'][] = $this->_name;
		}
		else
		{
			# multiple tags
			$options['conditions']["{$this->_referer}.{$this->_associations[$this->_referer]['primary_key']}"] =
				"-! IN ( ".$this->__format_sql(&$collection)." )";
		}
		return $options;
	}
	
	protected function __format_sql($collection)
	{
		$collection = array_collection($collection);
		
		# prepares for query
		$referer = $this->_associations[$this->_referer];
		$field   = $this->_db->field("{$this->_referer}.{$referer['primary_key']}");
		
		$base    = "SELECT $field ".
			" FROM {$referer['table']}  AS ".$this->_db->field($this->_referer).
			" LEFT JOIN {$this->_table} AS ".$this->_db->field($this->_name).
			" ON ".$this->_db->field("{$this->_name}.{$referer['foreign_key']}")." = $field".
			" WHERE ".$this->_db->field("{$this->_name}.{$this->_coll_field}")." = %s";
		
		# creates query
		$sql = sprintf($base, $this->_db->value(array_shift($collection)));
		$end = '';
		foreach($collection as $v)
		{
			$sql .= " AND $field IN ( ".sprintf($base, $this->_db->value($v));
			$end .= ' )';
		}
		return $sql.$end;
	}
	
	
	// CREATE & UPDATE
	
	function save($validate=null, $type=null)
	{
		# parameters
		$foreign_key = $this->_associations[$this->_referer]['foreign_key'];
		$conditions  = array($foreign_key => $this->$foreign_key);
		
		$field      = Inflector::pluralize($this->_coll_field);
		$collection = array_collection($this->$field);
		
		# erased all tags?
		if (empty($collection)) {
			return $this->delete_all(&$conditions);
		}
		
		# already sticked tags
		$_collection = $this->find(':all', array('conditions' => &$conditions));
		
		# removed tags
		$removed = array_diff($_collection, $collection);
		if (!empty($removed))
		{
			$conditions[$this->_coll_field] = $removed;
			if (!$this->delete_all(&$conditions)) {
				return false;
			}
		}
		
		# added
		$added = array_diff($collection, $_collection);
		if (!empty($added))
		{
			$fields = array($foreign_key, $this->_coll_field);
			$values = array();
			foreach($added as $one) {
				array_push($values, array($this->$foreign_key, $one));
			}
			return $this->_db->insert_many($this->_table, &$fields, &$values);
		}
		
		return true;
	}
}
?>
